// Thanks to Caolan McMahon. These codes blow come from his project Async(https://github.com/caolan/async).
define(function(require, exports, module) {

	var async = {};

	module.exports = async;

	// // cross-browser compatiblity functions ////

	var _forEach = function(arr, iterator) {
		if (arr.forEach) {
			return arr.forEach(iterator);
		}
		for ( var i = 0; i < arr.length; i += 1) {
			iterator(arr[i], i, arr);
		}
	};

	var _map = function(arr, iterator) {
		if (arr.map) {
			return arr.map(iterator);
		}
		var results = [];
		_forEach(arr, function(x, i, a) {
			results.push(iterator(x, i, a));
		});
		return results;
	};

	var _keys = function(obj) {
		if (Object.keys) {
			return Object.keys(obj);
		}
		var keys = [];
		for ( var k in obj) {
			if (obj.hasOwnProperty(k)) {
				keys.push(k);
			}
		}
		return keys;
	};

	// // exported async module functions ////

	// // nextTick implementation with browser-compatible fallback ////
	if (typeof process === 'undefined' || !(process.nextTick)) {
		async.nextTick = function(fn) {
			setTimeout(fn, 0);
		};
	} else {
		async.nextTick = process.nextTick;
	}

	async.forEach = function(arr, iterator, callback) {
		callback = callback || function() {
		};
		if (!arr.length) {
			return callback();
		}
		var completed = 0;
		_forEach(arr, function(x) {
			iterator(x, function(err) {
				if (err) {
					callback(err);
					callback = function() {
					};
				} else {
					completed += 1;
					if (completed === arr.length) {
						callback(null);
					}
				}
			});
		});
	};

	async.forEachSeries = function(arr, iterator, callback) {
		callback = callback || function() {
		};
		if (!arr.length) {
			return callback();
		}
		var completed = 0;
		var iterate = function() {
			iterator(arr[completed], function(err) {
				if (err) {
					callback(err);
					callback = function() {
					};
				} else {
					completed += 1;
					if (completed === arr.length) {
						callback(null);
					} else {
						iterate();
					}
				}
			});
		};
		iterate();
	};

	var doParallel = function(fn) {
		return function() {
			var args = Array.prototype.slice.call(arguments);
			return fn.apply(null, [ async.forEach ].concat(args));
		};
	};
	var doSeries = function(fn) {
		return function() {
			var args = Array.prototype.slice.call(arguments);
			return fn.apply(null, [ async.forEachSeries ].concat(args));
		};
	};

	var _asyncMap = function(eachfn, arr, iterator, callback) {
		var results = [];
		arr = _map(arr, function(x, i) {
			return {
				index : i,
				value : x
			};
		});
		eachfn(arr, function(x, callback) {
			iterator(x.value, function(err, v) {
				results[x.index] = v;
				callback(err);
			});
		}, function(err) {
			callback(err, results);
		});
	};
	async.map = doParallel(_asyncMap);
	async.mapSeries = doSeries(_asyncMap);

	async.series = function(tasks, callback) {
		callback = callback || function() {
		};
		if (tasks.constructor === Array) {
			async.mapSeries(tasks, function(fn, callback) {
				if (fn) {
					fn(function(err) {
						var args = Array.prototype.slice.call(arguments, 1);
						if (args.length <= 1) {
							args = args[0];
						}
						callback.call(null, err, args);
					});
				}
			}, callback);
		} else {
			var results = {};
			async.forEachSeries(_keys(tasks), function(k, callback) {
				tasks[k](function(err) {
					var args = Array.prototype.slice.call(arguments, 1);
					if (args.length <= 1) {
						args = args[0];
					}
					results[k] = args;
					callback(err);
				});
			}, function(err) {
				callback(err, results);
			});
		}
	};

});